package structures;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

import main.Initializable;
import main.InitializationException;
import main.Main;

public class Mitral implements Initializable
{
	private ArrayList<Column> columnsAffected;
	private HashMap<Granule, Double> granulesAffected;
	
	private boolean initialized = false;
	private int parentColumnIndex = -1;
	
	private double mitralToGranuleWeight = 1.00;
	
	/**
	 * Makes empty Mitral cell with default MTG Weight = 1.00
	 */
	public Mitral()
	{
		mitralToGranuleWeight = 1.00;
	}
	
	/**
	 * Makes Mitral cell with MTG weight drawn from distribution
	 * 
	 * @param g - Gaussian from which weight is taken
	 */
	public Mitral(util.Gaussian g)
	{
		this.mitralToGranuleWeight = g.getRandomValue();
	}
	
	/**
	 * Makes Mitral cell with specified MTG weight
	 * 
	 * @param mtgweight - the MTG weight
	 */
	public Mitral(double mtgweight)
	{
		this.mitralToGranuleWeight = mtgweight;
	}
	
	/**
	 * Chooses how many columns to affect in the network
	 * 
	 * Bounded from 0 to XX.XX% of the number of columns in the network
	 * 
	 * @param n - the network environment the columns exist in
	 */
	private void chooseColumnsToAffect(Network n)
	{
		//Choose X% of columns to affect
		Random r = new Random();
		
		//TODO Variable connectivity dependent on network variable
		int numToAffect = (int) Math.floor(n.getNumCols()*n.getMitralToColumnConnectivity());
		
		columnsAffected = new ArrayList<Column>(numToAffect);
		
		ArrayList<Integer> chosenIndexes = new ArrayList<Integer>();
		
		//Choose columns which to affect (exclude self)
		for (int x = numToAffect; x > 0; x--)
		{
			int indexChosen;
			
			do{
				
				indexChosen = r.nextInt(n.getNumCols());
				
			}while(chosenIndexes.contains(indexChosen) || indexChosen == this.parentColumnIndex);
			
			this.columnsAffected.add(n.getColumn(indexChosen));
			chosenIndexes.add(indexChosen);
		}
		
		//DEBUG
		if(main.Main.VERBOSE)
		{
			System.out.print("\nMitral cell "+this.toString()+" affects columns:");
			for(int a : chosenIndexes)
			{
				System.out.print(" "+a+" ");
			}
			System.out.println();
		}
		
		//LOG
		if (Main.LOG_WRITER != null)
		{
			Main.attemptNewLineToLog();
			Main.attemptWriteToLog("Mitral cell "+this.toString()+" affects columns:");
			for(int a : chosenIndexes)
			{
				Main.attemptWriteToLog(" "+a+" ");
			}
			Main.attemptNewLineToLog();
		}

		return;
	}

	public ArrayList<Column> getColumnsAffected()
	{
		return this.columnsAffected;
	}
	
	public double getToGranuleWeight(Granule g)
	{
		Double d = this.granulesAffected.get(g);
		
		if (d == null)
			return 0.0;
		else
			return d;
		
	}

	/**
	 * Determines effect this MitralCell has on Granules in other Columns
	 * @param n
	 */
	private void setMitralToGranuleWeights(Network n)
	{
		this.granulesAffected =  new HashMap<Granule, Double>(this.columnsAffected.size());
		this.mitralToGranuleWeight = n.getMitralToGranuleWeights().getRandomValue();
		
		for (Column c : this.columnsAffected)
		{
			for (Granule g : c.getGranules())
			{
				//MitralToGranuleWeight is internal field, default 1.00
				double weight = mitralToGranuleWeight; 
				
				this.granulesAffected.put(g, weight);
				
				//DEBUG
				if(main.Main.VERBOSE)
				{
					System.out.println("\t"+this.toString()+" weight to "+g.stringRepresentation+" is "+weight);
				}
				
				//LOG
				if (Main.LOG_WRITER != null)
				{
					Main.attemptNewLineToLog();
					Main.attemptWriteToLog(("\t"+this.toString()+" weight to "+g.stringRepresentation+" is "+weight));
				}
			}
		}
		
		return;
	}
	
	/**
	 * Initializes mitral cell connections, weights
	 * 
	 * In order:
	 * 	- Chooses columns which are to be affected by this mitral cell
	 *  - Sets the weights for connections to granule cells
	 * 
	 * @param network - the Network wherein this mitral cell lives
	 * 	 
	 * */
	public void initialize(Object network) throws InitializationException
	{
		Network n;
		if(network instanceof Network)
			n = (Network) network;
		else
			throw new InitializationException("Paramater not of type Network: Aborting Mitral initialization");
		
		//Choose which columns to affect
		this.chooseColumnsToAffect(n);
		
		//Choose granule weights
		this.setMitralToGranuleWeights(n);
		
		initialized = true;
	}
	
	public void setParentIndex(int n)
	{
		this.parentColumnIndex = n;
	}
	
	@Override
	public String toString()
	{
		return this.parentColumnIndex+"-M";
	}
	
	

	public boolean isInitialized() 
	{
		return this.initialized;
	}
}
